package com.ccys.app.service.impl;

import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSONObject;
import com.ccys.app.service.MemberPreAuthenticationService;
import com.ccys.app.service.MemberService;
import com.ccys.common.constants.*;
import com.ccys.common.enums.CodeEnum;
import com.ccys.common.third.mailGun.MailgunUtil;
import com.ccys.common.third.sms.SmsDto;
import com.ccys.common.third.sms.SmsUtil;
import com.ccys.common.third.twilio.TwilioVoiceCallUtil;
import com.ccys.common.util.*;
import com.ccys.common.vo.ResponseResultApp;
import com.ccys.db.cache.UserUtil;
import com.ccys.db.dto.login.*;
import com.ccys.db.dto.member.ActivatePinCodeDto;
import com.ccys.db.dto.member.GetTwilioTokenDto;
import com.ccys.db.dto.member.RequestPinCodeDto;
import com.ccys.db.dto.member.UpdatePasswordDto;
import com.ccys.db.entity.TblCountry;
import com.ccys.db.entity.TblDoctor;
import com.ccys.db.entity.TblMember;
import com.ccys.db.mapper.TblCountryMapper;
import com.ccys.db.mapper.TblDoctorMapper;
import com.ccys.db.mapper.TblMemberMapper;
import com.ccys.db.rely.service.impl.BaseServiceImpl;
import com.ccys.db.service.BaseSysCodeService;
import com.ccys.db.vo.member.ActivateAccountVO;
import com.ccys.db.vo.member.ConsultAddressVO;
import com.ccys.db.vo.member.LoginErrorVO;
import com.ccys.db.vo.member.ReminderVO;
import com.google.rpc.Code;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


@Slf4j
@Service
public class MemberPreAuthenticationServiceImpl extends BaseServiceImpl<TblMember> implements MemberPreAuthenticationService {

    @Autowired(required = false)
    private TblMemberMapper memberMapper;
    private static Lock lock = new ReentrantLock();
    @Autowired(required = false)
    private BaseSysCodeService baseSysCodeService;

    @Autowired(required = false)
    private TblCountryMapper tblCountryMapper;

    @Resource
    private TblDoctorMapper doctorMapper;


    @Override
    public Object login(LoginDto dto) {
        //password  为空时 返回的是html网页
        TblMember tblMember = null;
        LoginErrorVO loginErrorVO = new LoginErrorVO();
        loginErrorVO.setError("406");
        loginErrorVO.setError_description("Please try again or click on the Sign Up link below to create a new account with us");
        if(StringUtils.isEmpty(dto.getGrant_type())) {
            loginErrorVO.setError("unsupported_grant_type");
            loginErrorVO.setError_description(null);
            return loginErrorVO;
        }
        if("member".equals(dto.getAccount_type())) {
            if(StringUtil.isNotEmpty(dto.getEmail())) {
                //邮箱登录
                TblMember m = new TblMember();
                m.setEmail(dto.getEmail());
                m.setDelFlag(0);
                tblMember = this.memberMapper.selectOne(m);
                if(tblMember == null) {
                    return loginErrorVO;
                }
                if(StringUtil.isEmpty(dto.getPassword())) {
                    loginErrorVO.setError_description("Your account has been locked.");
                    loginErrorVO.setError("407");
                    return loginErrorVO;
                }
                String hash = PBKDF2Util.createHashPassword(dto.getPassword(),tblMember.getIterationPassword(),tblMember.getSaltPassword());
                // 校验登录密码
                boolean b = PBKDF2Util.verifyPassword(dto.getPassword(), tblMember.getIterationPassword(), hash);
                if(!b) {
                    //密码错误
                    return loginErrorVO;
                }

            }else {
                return ResponseResultApp.error();
            }
        }else{
            loginErrorVO.setError("401");
            loginErrorVO.setError_description("Request require authorization.");
        }
        if(tblMember.getLocked() == 1) {
            loginErrorVO.setError_description("Your account has been locked.");
            loginErrorVO.setError("407");
            return loginErrorVO;
        }

        TblMember m = new TblMember();
        m.setId(tblMember.getId());
        if(StringUtils.isNotEmpty(dto.getDevice_type()) && StringUtils.isNotEmpty(dto.getPush_notification_token())) {
            m.setDeviceId(dto.getPush_notification_token());
            //默认为 iOS  实际不知道iOS会传什么值
            int device_type = 1;
            if("Huawei".equals(dto.getDevice_type())) {
                device_type = 3;
            }else if("Android".equals(dto.getDevice_type())) {
                device_type = 2;
            }
            m.setDeviceType(device_type);

        }
        if("false".equals(dto.getStay_logged_in())) {
            m.setMemStayLoggedIn(false);
        }
        if("true".equals(dto.getStay_logged_in())) {
            m.setMemStayLoggedIn(true);
        }
        this.memberMapper.updateByPrimaryKeySelective(m);
        JSONObject data = new JSONObject();

        //有加密规则？jwt 加密算法  https://blog.csdn.net/qq_39044972/article/details/130953686
        // jwt 加密 https://www.cnblogs.com/moonlightL/p/10020732.html
        //access_token 的载荷信息
        //{"role":"member","nameid":"pI57O3WysbWuqhRhJ+YIv0VA7gcGfZZYx/0rE3QYl9NvP/y2o4m5m1iVMqmyxcsG","rfr-tkn":"19957596918676-23A53876-5AED-4B3D-B5DD-36934AAC124D","request_update_contact_info":"False","request_activate_account":"False","for_child_that_turned_21":"False",
        // "device_id":"","device_type":"3","iss":"https://whcapi-qa.whitecoat.global/","aud":"414e1927a3884f68abc79f7283837fd1","exp":1737020724,"nbf":1705484724}
        Map<String, Object> payload = new HashMap<>();
        payload.put("role", "member");
        payload.put("nameid",tblMember.getId());

        payload.put("rfr-tkn","?");
        payload.put("request_update_contact_info","False?");
        payload.put("request_activate_account","False?");
        payload.put("for_child_that_turned_21","False?");
        payload.put("device_id",tblMember.getDeviceId());
        payload.put("device_type",tblMember.getDeviceType().toString());
        payload.put("iss","https://whcapi-qa.whitecoat.global/");
        payload.put("aud","414e1927a3884f68abc79f7283837fd1?");
        //到期时间戳秒
        payload.put("exp",Kit.formatAddSecondTimeStamp(new Date(), ContextConstants.DEFAULT_PATIENT_EXPIRES_TIME.intValue()));
        //当前时间戳秒
        payload.put("nbf",System.currentTimeMillis() / 1000);
        String jwtToken = JwtUtil.createJwtToken(payload);
        data.put("access_token",jwtToken);
        String oldToken = JedisUtil.get(tblMember.getId(), CodeEnum.REDIS_USER.codeOf());
        if(StringUtils.isNotEmpty(oldToken) && !"dev".equals(oldToken)){
            JedisUtil.del(oldToken, CodeEnum.REDIS_USER.codeOf());
            JedisUtil.del(tblMember.getId(), CodeEnum.REDIS_USER.codeOf());
        }
        JedisUtil.setexObj(jwtToken, ContextConstants.DEFAULT_PATIENT_EXPIRES_TIME.intValue(), JSONObject.toJSON(tblMember), CodeEnum.REDIS_USER.codeOf());
        JedisUtil.setex(tblMember.getId(),ContextConstants.DEFAULT_PATIENT_EXPIRES_TIME.intValue(), jwtToken, CodeEnum.REDIS_USER.codeOf());
        //写死？
        data.put("token_type","bearer");
        //到期时间 单位秒 8位
        data.put("expires_in",ContextConstants.DEFAULT_PATIENT_EXPIRES_TIME);
        data.put(".issued",Kit.formatGmtDate(new Date()));
        data.put(".expires",Kit.formatAddSecondGmtDate(new Date(),ContextConstants.DEFAULT_NOT_OPERATED_TIME));
        return data;
    }

    @Override
    public ResponseResultApp forgotPassword(ForgotPasswordDto dto) {
        //校验国际邮箱是否邮箱格式
        if(StringUtils.isEmpty(dto.getEmail()) || !Kit.validateInternationalEmail(dto.getEmail())) {
            return ResponseResultApp.error(400,"The email address is invalid. Please re-enter your email address.");
        }
        String html = "";
        List<String> whereList = new ArrayList<>();
        whereList.clear();
        whereList.add("del_flag=0");
        whereList.add("email = '"+dto.getEmail()+"'");
        TblMember oneByLimit = this.findOneByLimit(TblMember.class, whereList);
        if(oneByLimit == null) {
            return ResponseResultApp.error("Your login ID is invalid. Please re-enter your email address / identification number.");
        }


        // 读取邮件模板
        try {
            String fullName = oneByLimit.getFirstName();
            if(StringUtil.isNotEmpty(oneByLimit.getLastName())) {
                fullName = fullName + " " + oneByLimit.getLastName();
            }
            String templateContent = HtmlUtils.getTemplateContent("Account_Recovery.ftl", null);
            //替换内容
            templateContent = templateContent.replace("[Patient_Name]", fullName);

            //判断是否有没有使用的修改密码令牌
            String token = JedisUtil.get("forgotPassword:"+oneByLimit.getId(), CodeEnum.REDIS_UPDATE_PROFILE.codeOf());
            if(StringUtils.isNotBlank(token)){
                JedisUtil.del(token,CodeEnum.REDIS_UPDATE_PROFILE.codeOf());
            }
            token = IdUtil.fastUUID();
            JedisUtil.set("forgotPassword:"+oneByLimit.getId(),token,CodeEnum.REDIS_UPDATE_PROFILE.codeOf());
            JSONObject json = new JSONObject();
            json.put("type","member");
            json.put("id",oneByLimit.getId());
            JedisUtil.set(token,json.toJSONString(),CodeEnum.REDIS_UPDATE_PROFILE.codeOf());

            //TODO 替换重置密码路径  前端需要提供网页地址
            String url = "www.whitecoat.global";
            templateContent = templateContent.replace("[Reset_Password_Url]", url+"?token="+token);

            html = templateContent;
        } catch (Exception e) {
            e.printStackTrace();
        }
        boolean b = MailgunUtil.sendEmail(dto.getEmail(), "Forgot Your Password?","Did you request this password reset? If you suspect any suspicious activity, please get in touch with us at contactus@whitecoat.global",html);
        if(b) {
            return ResponseResultApp.success("We have received your request. Please check your email and follow the instructions to reset your password.",null);
        }else{
            return ResponseResultApp.error(400,"Please try again after 5 minutes !");
        }

    }

    @Override
    public ResponseResultApp resetPassword(ResetPasswordDto dto) {
        if(StringUtils.isEmpty(dto.getToken()) || StringUtils.isEmpty(dto.getNew_password())) {
            return ResponseResultApp.error();
        }

        //判断token是否有效
        String jsonStr = JedisUtil.get(dto.getToken(), CodeEnum.REDIS_UPDATE_PROFILE.codeOf());
        if(StringUtils.isBlank(jsonStr)){
            return ResponseResultApp.error("Invalid, please operate again.");
        }
        if(!JedisUtil.lock(dto.getToken(),jsonStr,3000L,CodeEnum.REDIS_LOCK.codeOf())){
            return ResponseResultApp.error("Operation timeout,please try again.");
        }

        Integer iterationPassword = PBKDF2Util.generateIterationPassword();
        String saltPassword = PBKDF2Util.generateSaltPassword();
        String hashPassword = PBKDF2Util.createHashPassword(dto.getNew_password(), iterationPassword, saltPassword);

        JSONObject json = JSONObject.parseObject(jsonStr);
        String id = json.getString("id");

        int i = 0;
        if("member".equals(json.getString("type"))){
            TblMember oneByLimit = memberMapper.selectByPrimaryKey(id);
            if(oneByLimit != null) {
                TblMember member = new TblMember();
                member.setId(oneByLimit.getId());
                member.setPassword(hashPassword);
                member.setIterationPassword(iterationPassword);
                member.setSaltPassword(saltPassword);
                i = this.memberMapper.updateByPrimaryKeySelective(member);
            }
        }else{
            TblDoctor doctor = new TblDoctor();
            doctor.setId(id);
            doctor.setPassword(hashPassword);
            doctor.setIterationPassword(iterationPassword);
            doctor.setSaltPassword(saltPassword);
            i = doctorMapper.updateByPrimaryKeySelective(doctor);
        }

        if(i > 0) {
            String oldToken = JedisUtil.get(id, CodeEnum.REDIS_USER.codeOf());
            if(StringUtils.isNotEmpty(oldToken) && !"dev".equals(oldToken)){
                JedisUtil.del(oldToken, CodeEnum.REDIS_USER.codeOf());
                JedisUtil.del(id, CodeEnum.REDIS_USER.codeOf());
            }
        }

        //删除token
        JedisUtil.del(dto.getToken(),CodeEnum.REDIS_UPDATE_PROFILE.codeOf());
        JedisUtil.del("forgotPassword:"+id, CodeEnum.REDIS_UPDATE_PROFILE.codeOf());

        JedisUtil.unlock(dto.getToken(),id,CodeEnum.REDIS_LOCK.codeOf());
        return ResponseResultApp.success("Reset password successfully.",null);
    }

    @Override
    public ResponseResultApp register(RegisterDto dto) {

        /*
        {
            "errorCode": 400,
            "message": "Required property 'country_of_residence' not found in JSON. Path '', line 22, position 1.",
            "data": null
        }
         */
        Date dob = Kit.parseDateTimeStr(dto.getDate_of_birth(), "yyyy-MM-dd");
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(dob);
        int dobYear = calendar.get(Calendar.YEAR);
        int min_age = 16, max_age = 150;
        LocalDate localDate = LocalDate.now();
        int currentYear = localDate.getYear();
        int age = currentYear - dobYear;
        if (age < min_age){
            //apiResult = new ApiResult(ErrorCodes.BAD_REQUEST, string.Format(Message.YOU_HAVE_TO_BE_AGE_YEARS_OLD_TO_REGISTER_AN_ACCOUNT, min_age), null);
            return ResponseResultApp.error(400,"Message.YOU_HAVE_TO_BE_AGE_YEARS_OLD_TO_REGISTER_AN_ACCOUNT");
        } else if (age == min_age && localDate.getMonthValue() < calendar.get(Calendar.MONTH)) {
            //apiResult = new ApiResult(ErrorCodes.BAD_REQUEST, string.Format(Message.YOU_HAVE_TO_BE_AGE_YEARS_OLD_TO_REGISTER_AN_ACCOUNT, min_age), null);
            return ResponseResultApp.error(400,"Message.YOU_HAVE_TO_BE_AGE_YEARS_OLD_TO_REGISTER_AN_ACCOUNT");
        }
        else if (age == min_age && localDate.getMonthValue() == calendar.get(Calendar.MONTH) && localDate.getDayOfMonth() < calendar.get(Calendar.DATE)) {
            //apiResult = new ApiResult(ErrorCodes.BAD_REQUEST, string.Format(Message.YOU_HAVE_TO_BE_AGE_YEARS_OLD_TO_REGISTER_AN_ACCOUNT, min_age), null);
            return ResponseResultApp.error(400,"Message.YOU_HAVE_TO_BE_AGE_YEARS_OLD_TO_REGISTER_AN_ACCOUNT");
        } else if ((age  > max_age)) {
            //apiResult = new ApiResult(ErrorCodes.BAD_REQUEST, string.Format(Message.AGE_SHOULD_BE_LESS_THAN_MAX_AGE_YEARS, max_age), null);
            return ResponseResultApp.error(400,"Message.YOU_HAVE_TO_BE_AGE_YEARS_OLD_TO_REGISTER_AN_ACCOUNT");
        }
        if(StringUtil.isEmpty(dto.getCountry_of_residence())) {
            return ResponseResultApp.error(400,"Required property 'country_of_residence' not found in JSON. Path '', line 22, position 1.");
        }
        if(StringUtil.isEmpty(dto.getEmail())) {
            return ResponseResultApp.error(400,"The email field is required.");
        }
        if(StringUtil.isEmpty(dto.getPassword()) || dto.getPassword().length() < 8 || !Kit.isValidPassword(dto.getPassword())) {
            return ResponseResultApp.error(511,"Password must meet all of the following requirements:\\r\\n 1. Minimum length: 8 characters\\r\\n 2. Password must be alphanumeric, include a mixture of upper and lowercase letters, and include special characters\\r\\n 3. Must not be any of your last 5 passwords");
        }


        TblMember member = new TblMember();
        lock.lock();
        try {
            member.setEmail(dto.getEmail());
            List<String> whereList = new ArrayList<>();
            whereList.clear();
            whereList.add("del_flag=0");
            whereList.add("email='"+dto.getEmail()+"'");
            TblMember oneByLimit = this.findOneByLimit(TblMember.class, whereList);
            if(oneByLimit != null) {
                return ResponseResultApp.error(411,"The email address you have entered is already in use. Please try again.",dto);
            }

            whereList.clear();
            whereList.add("del_flag=0");
            whereList.add("nric_fin='"+dto.getNric_fin()+"'");
            oneByLimit = this.findOneByLimit(TblMember.class, whereList);
            if(oneByLimit != null) {
                return ResponseResultApp.error(411,"The nric fin you have entered is already in use. Please try again.",dto);
            }
            whereList.clear();
            whereList.add("del_flag=0");
            whereList.add("phone='"+dto.getPhone()+"'");
            oneByLimit = this.findOneByLimit(TblMember.class, whereList);
            if(oneByLimit != null) {
                return ResponseResultApp.error(411,"The phone you have entered is already in use. Please try again.",dto);
            }

        } finally {
            lock.unlock();
        }

        Integer iterationPassword = PBKDF2Util.generateIterationPassword();
        String saltPassword = PBKDF2Util.generateSaltPassword();
        String hashPassword = PBKDF2Util.createHashPassword(dto.getPassword(), iterationPassword, saltPassword);
        member.setPassword(hashPassword);
        member.setIterationPassword(iterationPassword);
        member.setSaltPassword(saltPassword);
        member.setFirstName(dto.getFirst_name());
        member.setLastName(dto.getLast_name());
        if(StringUtils.isNotEmpty(dto.getDate_of_birth())) {
            try {
                member.setDob(dob);
            }catch (Exception e) {
                e.printStackTrace();
            }

        }
        member.setNricFin(dto.getNric_fin());
        member.setPhone(dto.getPhone());
//        member.setHomeAddress(dto.getHome_address());
        member.setLocationLatitude(dto.getLatitude() == null ? null : new BigDecimal(dto.getLatitude()));
        member.setLocationLongitude(dto.getLongitude() == null ? null : new BigDecimal(dto.getLongitude()));
        //  unit_number ??, 前端没传不管
        if("male".equals(dto.getGender())) {
            member.setGender(1);
        } else  if("female".equals(dto.getGender())) {
            member.setGender(0);
        }else{
            member.setGender(-1);
        }
        member.setDeviceId(dto.getPush_notification_token());
        //设备 1ios 2 android 3 Huawei
        if(StringUtils.isNotEmpty(dto.getDevice_type())) {
            if("ios".equalsIgnoreCase(dto.getDevice_type())) {
                member.setDeviceType(1);
            }else if("Android".equalsIgnoreCase(dto.getDevice_type())) {
                member.setDeviceType(2);
            }else  if("Huawei".equalsIgnoreCase(dto.getDevice_type())) {
                member.setDeviceType(3);
            }
        }
        if(StringUtils.isNotEmpty(dto.getNews_letter())) {
            if("true".equalsIgnoreCase(dto.getNews_letter())) {
                member.setNewsLetter(1);
            }else if("false".equalsIgnoreCase(dto.getNews_letter())) {
                member.setNewsLetter(0);
            }
        }
        member.setCurrentCountry(dto.getCountry_id());
        member.setLocationState(dto.getState());
        //type_id ?? , 前端没传不管
        member.setBackIdentificationPhoto(dto.getBack_identification_card_photo());
        member.setFrontIdentificationPhoto(dto.getFront_identification_card_photo());
        member.setCurrentCountry(dto.getCountry_of_residence());
        member.setCurrentLanguage(dto.getLanguage_code());
        // mem_id ?  怎么使用 TODO
        member.setNationality(dto.getNationality());
        member.setPhoneCountryId(dto.getPhone_country_id());
        member.setMemberNo(getUserNumber());

        //member.setStatus(?);
        member.setFailedPasswordAttempts(0);
        //member.setActived(?);
        //账号关系有默认值？
        //member.setRelationship(？);
        //是否测试账号
        //member.setTestAccount(0);


        this.saveEntity(member);
        // 读取邮件模板
        try {
            String fullName = member.getFirstName();
            if(StringUtil.isNotEmpty(member.getLastName())) {
                fullName = fullName + " " + member.getLastName();
            }
            String templateContent = HtmlUtils.getTemplateContent("Register_Account.ftl", null);
            templateContent = templateContent.replace("[Patient_Name]", fullName);
            //异步处理邮件发送业务
            String finalTemplateContent = templateContent;
            CompletableFuture.runAsync(() -> {
            MailgunUtil.sendEmail(dto.getEmail(), "WhiteCoat Account Successfully Created!",null, finalTemplateContent);
            });
        } catch (Exception e) {
            e.printStackTrace();
        }

        // delete draft account
        /*if (!String.IsNullOrEmpty(model.mem_id))
        {
            //删除草稿账号
            memberHelper.deleteAccount(model.mem_id);
        }else{
            //更新草稿账号
            object responseData = UpdateDraftAccount(draftAccount).data;
        }

        */
        ActivateAccountVO activateAccountInfo = this.memberMapper.getActivateAccountInfo(member.getId());
        return ResponseResultApp.success(activateAccountInfo);
    }

    /*public bool IsHitRateLimit(string phoneCountryId, string phoneNo)
    {
        var countryDataSet = GetCountryByDial(phoneCountryId);
        if (countryDataSet == null || countryDataSet.Tables.Count < 1)
            return true;
        bool.TryParse(countryDataSet.Tables[0].Rows[0]["is_sms_whitelist_country"].ToString(), out var isSmsWhitelistCountry);
        if (isSmsWhitelistCountry)
            return false;

        int.TryParse(countryDataSet.Tables[0].Rows[0]["allowance"].ToString(), out var allowance);
        int.TryParse(countryDataSet.Tables[0].Rows[0]["last_sms_time_stamp"].ToString(), out var lastSmsTimeStamp);
        int.TryParse(GetConfig("SMS_Limit_Count"), out var smsLimitCount);
        int.TryParse(GetConfig("SMS_BLOCK_DURATION_IN_SECONDS"), out var duration);
        int.TryParse(countryDataSet.Tables[0].Rows[0]["id"].ToString(), out var countryId);
        var timeNow = DateTimeOffset.Now.ToUnixTimeSeconds();
        allowance += (int)((timeNow - lastSmsTimeStamp) * smsLimitCount / duration);
        if (allowance > smsLimitCount)
            allowance = smsLimitCount;
        if (allowance < 1)
        {
            UpdateSMSAllowance(timeNow, countryId, 0);
            return true;
        }
        UpdateSMSAllowance(timeNow, countryId, allowance - 1);
        return false;
    }*/
    @Override
    public ResponseResultApp requestOTP(RequestOTPDto dto) {
        if(StringUtil.isEmpty(dto.getType())) {
            //按照原逻辑设置默认值
            dto.setType("activate_account");
        }
        Map<String,Object> resultMap = new HashMap<String,Object>();
        String verified_code = this.baseSysCodeService.getCodeValue("verified_otp_code", "1");
        if(StringUtil.isEmpty(verified_code)) {
            return ResponseResultApp.error("SiteConfig not set ");
        }
        //参数校验？ 数据合法性校验？
        if(StringUtil.isEmpty(dto.getKey())) {
            return ResponseResultApp.error("key is not null");
        }
        String plainkey = verified_code + "." + dto.getPhone();
        String hashkey = Kit.computeSha256Hash(plainkey);
        if (!dto.getKey().equalsIgnoreCase(hashkey)){
            return ResponseResultApp.error("Mobile number could not be found.");
        }
        //TODO  错误提未提供
        if(StringUtil.isEmpty(dto.getPhone_country_id())) {
            return ResponseResultApp.error("EXCEED_MAXIMUM_NUMBER_OF_RESEND_OTP");
        }
        TblCountry tblCountry = this.tblCountryMapper.selectByPrimaryKey(dto.getPhone_country_id());
        if(tblCountry == null) {
            return ResponseResultApp.error("EXCEED_MAXIMUM_NUMBER_OF_RESEND_OTP");
        }
        if(tblCountry.getIsSmsWhitelistCountry() < 1) {
            return ResponseResultApp.error("EXCEED_MAXIMUM_NUMBER_OF_RESEND_OTP");
        }


        List<String> whereList = new ArrayList<>();
        whereList.clear();
        whereList.add("del_flag=0");
        whereList.add("phone = '"+dto.getPhone()+"'");
        TblMember member = this.findOneByLimit(TblMember.class, whereList);
        if(StringUtil.isEmpty(dto.getPhone()) || member == null) {
            return ResponseResultApp.error("Mobile number could not be found.");
        }
        if(member.getActived() == 1) {
            return ResponseResultApp.error("ACCOUNT_ALREADY_ACTIVATE");
        }
        String code = Kit.getMsgCode(4);
        if(member.getTestAccount() != null && member.getTestAccount() == 1) {
            //测试账号校验码写死
            code = "1234";
        }
        String codeOld = JedisUtil.get(dto.getPhone() + "msgCode", CodeEnum.REDIS_MSG_CODE.codeOf());
        if (StringUtils.isNotEmpty(codeOld)) {
            //等待移动倒计时结束后再次发送验证码
            return ResponseResultApp.error("MOBILE_COUNTDOWN_ENDS");
        }
        //越南人使用的 pincode？？
        //String time_out_resend_pin_code = this.baseSysCodeService.getCodeValue("Time_Out_Resend_Pin_Code", "2");
        String time_Out_Resend_Activate_Code = this.baseSysCodeService.getCodeValue("Time_Out_Resend_Activate_Code", null);

        if(StringUtil.isEmpty(time_Out_Resend_Activate_Code)) {
            return ResponseResultApp.error("SiteConfig exception");
        }
        //更新用户重发验证码次数
        TblMember tblMemberUpdate = new TblMember();
        tblMemberUpdate.setId(member.getId());
        tblMemberUpdate.setRequestActiveDate(new Date());
        tblMemberUpdate.setActiveCode(code);
        tblMemberUpdate.setNumberOfResendOtp(member.getNumberOfResendOtp() == null ? 1 : member.getNumberOfResendOtp() +1);
        this.updateEntityById(tblMemberUpdate);
        Integer numberOfResendOtpMax = 5;
        String number_of_resend_otp = this.baseSysCodeService.getCodeValue("number_of_resend_otp", null);
        if(StringUtil.isNotEmpty(number_of_resend_otp)) {
            numberOfResendOtpMax = Integer.parseInt(number_of_resend_otp);
        }
        if(member.getNumberOfResendOtp() != null && member.getNumberOfResendOtp() > numberOfResendOtpMax) {
            //EXCEED_ATTEMPT_OF_RETRY_OTP  重试次数超过OTP
            return ResponseResultApp.error("EXCEED_ATTEMPT_OF_RETRY_OTP");
        }

        System.out.println("======================验证码："+code+"==================");
        SmsUtil.sendSmsContent(new SmsDto(null,tblCountry.getDial(),dto.getPhone(),code));

        //将验证码存入缓存
        JedisUtil.set(dto.getPhone() + "msgCode", code, CodeEnum.REDIS_MSG_CODE.codeOf());

        JedisUtil.expire(dto.getPhone() + "msgCode", Integer.parseInt(time_Out_Resend_Activate_Code)*60, CodeEnum.REDIS_MSG_CODE.codeOf());
        resultMap.put("code", null);

        return ResponseResultApp.success("RESEND_OTP_SUCCESS",Kit.formatDateTime(new Date(),"yyyy/MM/dd HH:mm:ss"));
    }

    @Override
    public ResponseResultApp activateAccount(ActivateAccountDto dto) {
        boolean flag = true;
        //验证码错误次数
        Integer errorNum = 0;
        String errorStr = "";
        if(StringUtil.isEmpty(dto.getPhone())) {
            return ResponseResultApp.error("The One time password field is required.");
        }
        if(StringUtil.isEmpty(dto.getOne_time_password())) {
            return ResponseResultApp.error("The phone field is required.");
        }
        TblMember curLoginMember = UserUtil.getCurLoginMember();
        List<String> whereList = new ArrayList<>();
        whereList.clear();
        whereList.add("del_flag=0");
        whereList.add("phone = '"+dto.getPhone()+"'");
        if(StringUtil.isNotEmpty(dto.getPhone_country_id())) {
            whereList.add("country_id = '"+dto.getPhone_country_id()+"'");
        }

        if(curLoginMember == null) {
            curLoginMember = this.findOneByLimit(TblMember.class,whereList);
        }
        if(curLoginMember == null) {
            //TODO '0'  对应的result
            return ResponseResultApp.error("phone absent");
        }

        String code = JedisUtil.get(dto.getPhone() + "msgCode", CodeEnum.REDIS_MSG_CODE.codeOf());
        if (StringUtils.isEmpty(code)) {
            flag = false;
            errorStr = "The One time password field is expire.";
        }
        if (!code.equals(dto.getOne_time_password())) {
            flag = false;
            errorStr = "The One time password field is error.";
        }

        //超时等待时间分钟
        int waiting_time_of_exceeding_number_resending_otp = 10;
        // Time_Out_Resend_Activate_Code  获取系统参数中的值  默认值是3
        String id = curLoginMember.getId();
        String check_Actived = id;
        String check_Code = curLoginMember.getActiveCode();
        //输错验证码次数
        Integer numberOfRetryOtp = curLoginMember.getNumberOfRetryOtp();
        if(numberOfRetryOtp == null) {
            numberOfRetryOtp = 0;
        }
        //最后输错验证码时间
        Date lastTimeRetryOtp = curLoginMember.getLastTimeRetryOtp();
        Integer actived = curLoginMember.getActived();
        if(actived == 1) {
            //TODO '-1'  对应的result 已经是激活状态 给出提示
            return ResponseResultApp.error("error.");
        }
        int waiting_time = 0;
        if(lastTimeRetryOtp != null) {
            long l = lastTimeRetryOtp.getTime() / 1000;
            long currentTimeMillis = System.currentTimeMillis()/1000;
            long l1 = currentTimeMillis - l;
            //在10分钟内输错三次
            if(numberOfRetryOtp > 3 && l1 < (60 * waiting_time_of_exceeding_number_resending_otp)) {
                BigDecimal bigDecimal = BigDecimal.valueOf(l1).divide(BigDecimal.valueOf(60)).setScale(0, RoundingMode.CEILING);
                //已经等待时间
                waiting_time = bigDecimal.intValue();
                waiting_time = 10 - waiting_time;
                //TODO '-4'  对应的result
                flag = false;
                errorStr = "error.";
            }
        }
        if(StringUtil.isEmpty(check_Code)) {
            //校验码为空 更新
            flag = false;
            if(curLoginMember.getNumberOfRetryOtp() == null || curLoginMember.getNumberOfRetryOtp() > 3) {
                errorNum = 1;
            }else{
                errorNum = curLoginMember.getNumberOfRetryOtp() + 1;
            }
            // -2 对应的result
            errorStr = "error.";

        }

        Date requestActiveDate = curLoginMember.getRequestActiveDate();
        // 验证码超时时间 （计算验证码是否超时）
        String time_out_resend_activate_code = this.baseSysCodeService.getCodeValue("Time_Out_Resend_Activate_Code", null);
        //代替上面校验 的数据库校验
        if (StringUtils.isEmpty(code) || !code.equals(dto.getOne_time_password())) {
            //requestActiveDate 为空等价于 code 空   验证码过期
            flag = false;
            //-3 对应的result
            errorStr = "The One time password field is expire.";
        }


        if(!flag) {
            //校验不通过
            TblMember t = new TblMember();
            t.setId(curLoginMember.getId());
            t.setNumberOfRetryOtp(errorNum);
            t.setLastTimeRetryOtp(new Date());
            this.updateEntityById(t);
            return ResponseResultApp.error(errorStr);
        }else{
            //校验通过
            this.memberMapper.updateActivateAccount(curLoginMember.getId());
        }

        if(StringUtils.isNotEmpty(code)){
            //清空缓存的校验码
            JedisUtil.del(code, CodeEnum.REDIS_USER.codeOf());
            JedisUtil.del(curLoginMember.getId(), CodeEnum.REDIS_USER.codeOf());
        }
        ActivateAccountVO activateAccountVO = this.memberMapper.getActivateAccountInfo(curLoginMember.getId());
        //新注册的账号激活  经测试数据为空
        activateAccountVO.setAllergies(new ArrayList<>());
        activateAccountVO.setReminder(new ReminderVO());
        activateAccountVO.setConsultAddress(new ConsultAddressVO());
        activateAccountVO.setMemberConsultProfiles(new ArrayList<>());
        return ResponseResultApp.success(activateAccountVO);
    }

    @Override
    public ResponseResultApp requestPinCode(RequestPinCodeDto dto) {
        if(StringUtil.isEmpty(dto.getEmail())) {
            return ResponseResultApp.error("email is  requisite");
        }
        TblMember curLoginMember = UserUtil.getCurLoginMember();
        if(curLoginMember == null) {
            List<String> whereList = new ArrayList<>();
            whereList.clear();
            whereList.add("del_flag=0");
            whereList.add("email = '"+dto.getEmail()+"'");
            curLoginMember = this.findOneByLimit(TblMember.class,whereList);
            if(curLoginMember == null) {
                return ResponseResultApp.error("Please log in");
            }

        }
        String fullName = curLoginMember.getFirstName();
        if(StringUtil.isNotEmpty(curLoginMember.getLastName())) {
            fullName = fullName + curLoginMember.getLastName();
        }
        String randomNumber = Kit.getRandomNumber(6);
        if(curLoginMember.getTestAccount() != null && curLoginMember.getTestAccount() == 1) {
            //测试账号校验码写死
            randomNumber = "123456";
        }
        //从新发送时间单位
        String time_out_resend_pin_code = this.baseSysCodeService.getCodeValue("Time_Out_Resend_Pin_Code", "2");
        if(StringUtil.isEmpty(time_out_resend_pin_code)) {
            time_out_resend_pin_code = "5";
        }
        int time_out_resend_pin_code_second = Integer.parseInt(time_out_resend_pin_code) * 60;
        //将验证码存入缓存
        JedisUtil.setex(dto.getEmail() + "pinCode",time_out_resend_pin_code_second, randomNumber, CodeEnum.REDIS_MSG_CODE.codeOf());
        TblMember tblMember = new TblMember();
        tblMember.setId(curLoginMember.getId());
        //pingcode 激活码也是用该字段
        tblMember.setActiveCode(randomNumber);
        tblMember.setRequestActiveDate(new Date());
        this.updateEntityById(tblMember);
        sendRequestPinCodeEmail(dto.getEmail(),fullName,randomNumber,curLoginMember.getCurrentCountry());
        JSONObject json = new JSONObject();
        Date date = Kit.plusSecond(time_out_resend_pin_code_second, new Date());
        json.put("resend_otp_possible",Kit.formatGmtDate(date, "yyyy/MM/dd HH:mm:ss"));
        json.put("countdown_timer_resend_otp", time_out_resend_pin_code_second);
        return ResponseResultApp.success("Your OTP has been sent via email to your email.",json);
    }

    @Override
    public ResponseResultApp activatePinCode(ActivatePinCodeDto dto) {
        if(StringUtil.isEmpty(dto.getPhoneCountryId())) {
            dto.setPhoneCountryId(CoreAppConstants.COUNTRY_SINGAPORE_ID);
        }
        ResponseResultApp responseResultApp = new ResponseResultApp();
        String msg = "";
        int errorCode = 400;
        JSONObject resultData = new JSONObject();
        List<String> whereList = new ArrayList<>();
        TblMember curLoginMember = UserUtil.getCurLoginMember();
        if (curLoginMember == null) {
            whereList.clear();
            whereList.add("del_flag=0");
            whereList.add("email = '"+dto.getEmail()+"'");
            curLoginMember = this.findOneByLimit(TblMember.class, whereList);
            if(curLoginMember == null) {
                whereList.clear();
                whereList.add("del_flag=0");
                whereList.add("phone = '"+dto.getEmail()+"'");
                whereList.add("country_id = '"+dto.getPhoneCountryId()+"'");
                curLoginMember = this.findOneByLimit(TblMember.class, whereList);
                if(curLoginMember == null) {
                    msg = "ErrorString.MOBILE_NOT_FOUND";
                    responseResultApp.setMessage("ErrorString.MOBILE_NOT_FOUND");
                }
            }
        }
        if (curLoginMember != null) {
            String code = JedisUtil.get(dto.getEmail() + "pinCode", CodeEnum.REDIS_MSG_CODE.codeOf());
            resultData.put("attempt",curLoginMember.getNumberOfRetryOtp());
            resultData.put("max_attempt",CoreAppConstants.MAX_ATTEMPT_OF_RETRY_OTP);
            if(StringUtil.isEmpty(curLoginMember.getActiveCode()) || !curLoginMember.getActiveCode().equals(dto.getOneTimePassword())) {
                if (curLoginMember.getNumberOfRetryOtp() != null && curLoginMember.getNumberOfRetryOtp()  >= 3 ) {
                    int min = valid(resultData, curLoginMember);
                    //if(min < 错误等待时长){} 超过激活错误10分钟可以继续激活 （源代码删除了改逻辑）
                    msg = "ErrorString.EXCEED_ATTEMPT_OF_RETRY_OTP," + min;
                }else {
                    msg = "ErrorCodes.OTP_IS_INCORRECT";
                }
            }else if(StringUtil.isEmpty(code) || !code.equals(dto.getOneTimePassword())) {
                if (curLoginMember.getNumberOfRetryOtp() != null && curLoginMember.getNumberOfRetryOtp()  >= 3) {
                    int min = valid(resultData, curLoginMember);
                    msg = "ErrorString.EXCEED_ATTEMPT_OF_RETRY_OTP," + min;
                } else {
                    msg = "ErrorCodes.OTP_IS_EXPIRED";
                }
            }else {
                //pincode 激活成功 （源代码激活pingcode 用到的仍然是 otp的相关字段，并没有使用pingcode相关的字段）
                this.memberMapper.updateActivatePincode(curLoginMember.getId());
                errorCode = 0;
                msg = "Success";
                String plainkey = dto.getOneTimePassword() + "." + dto.getEmail();
                String hashkey = Kit.computeSha256Hash(plainkey);
                responseResultApp.setData(hashkey);
                responseResultApp.setMessage(msg);
                responseResultApp.setErrorCode(errorCode);
                return responseResultApp;

            }
        }
        //校验不通过
        int errorNum = 0;
        if(curLoginMember.getNumberOfRetryOtp() == null || curLoginMember.getNumberOfRetryOtp() > 3) {
            errorNum = 1;
        }else{
            errorNum = curLoginMember.getNumberOfRetryOtp() + 1;
        }
        TblMember t = new TblMember();
        t.setId(curLoginMember.getId());
        //原逻辑数据库函数实际上是每次写0，并没有记录错误次数 errorNum
        t.setNumberOfRetryOtp(0);
        t.setLastTimeRetryOtp(new Date());
        this.updateEntityById(t);
        responseResultApp.setData(resultData);
        responseResultApp.setMessage(msg);
        responseResultApp.setErrorCode(errorCode);

        return responseResultApp;
    }

    @Override
    public ResponseResultApp requestPinCodeViaSMS(String key) {
        TblMember memberInfo = UserUtil.getCurLoginMember();
        if(memberInfo == null) {
            return ResponseResultApp.error("Please log in");
        }
        String phone = memberInfo.getPhone();
        String phone_country_id = memberInfo.getPhoneCountryId();
        Integer numberOfResendOtp = memberInfo.getNumberOfResendOtp();
        Date requestActiveDate = memberInfo.getRequestActiveDate();
        TblCountry country = this.tblCountryMapper.selectByPrimaryKey(phone_country_id);
        if(country == null) {
            return ResponseResultApp.error("country does not exist");
        }
        if ("false".equals(AppSettingsConstants.sendSms)) {
            return ResponseResultApp.error("Cannot send SMS");
        }
        //10等待重发时间10分钟
        int waiting_time_of_exceeding_number_resending_otp = 10;
        ResponseResultApp responseResultApp = new ResponseResultApp();
        String msg = "";
        int errorCode = 400;
        boolean flag = true;
        JSONObject resultData = new JSONObject();
        String verified_code = this.baseSysCodeService.getCodeValue("verified_otp_code",null);
        String plainkey = verified_code + "." + phone;
        String hashkey = Kit.computeSha256Hash(plainkey);
        String randomNumber = Kit.getRandomNumber(6);
        if(memberInfo.getTestAccount() != null && memberInfo.getTestAccount() == 1) {
            //测试账号校验码写死
            randomNumber = "123456";
        }
        if (key.equalsIgnoreCase(hashkey)) {
            if (isHitRateLimit(phone_country_id)){
                msg = "Please Try again";
                flag = false;
            }

            String time_out_resend_pin_code = this.baseSysCodeService.getCodeValue("Time_Out_Resend_Pin_Code", null);
            if (StringUtil.isEmpty(time_out_resend_pin_code)) {
                time_out_resend_pin_code = "5";
            }
            //可能重发时间（秒）
            int time_resend_otp_possible = Integer.parseInt(time_out_resend_pin_code) *60;
            //上次可能重发时间
            //int previous_time_resend_otp_possible = time_resend_otp_possible;
            if(CoreAppConstants.COUNTRY_SINGAPORE_ID.equals(phone_country_id)) {
                //病人国家是新加坡 特殊处理
                if(numberOfResendOtp != null) {
                    switch (numberOfResendOtp){
                        case 0:
                            time_resend_otp_possible = 30;
                            break;
                        case 1:
                            time_resend_otp_possible = 3*60;
                            break;
                        case 2:
                            time_resend_otp_possible = 10*60;
                            break;
                        default:
                            time_resend_otp_possible = 60*60;
                            break;
                    }
                }
            }

            if(requestActiveDate != null) {
                long l = requestActiveDate.getTime() / 1000;
                long currentTimeMillis = System.currentTimeMillis()/1000;
                long l1 = currentTimeMillis - l;
                //在10分钟内重发5次
                if(numberOfResendOtp > 5 && l1 < (60 * waiting_time_of_exceeding_number_resending_otp)) {
                    //TODO '-4'  对应的result
                    resultData.put("countdown_timer_resend_otp",l1);
                    Date date = Kit.plusSecond((int)l1, new Date());
                    resultData.put("resend_otp_possible",Kit.formatGmtDate(date, "yyyy/MM/dd HH:mm:ss"));
                    msg = "EXCEED_MAXIMUM_NUMBER_OF_RESEND_OTP.";
                    flag = false;
                }else{
                    String code = JedisUtil.get(phone + "pinCode", CodeEnum.REDIS_MSG_CODE.codeOf());
                    if(StringUtil.isNotEmpty(code)) {
                        //上次发送的pingcode 未过期 又再次发送
                        //TODO '-3'  对应的result
                        resultData.put("countdown_timer_resend_otp",l1);
                        Date date = Kit.plusSecond((int)l1, new Date());
                        resultData.put("resend_otp_possible",Kit.formatGmtDate(date, "yyyy/MM/dd HH:mm:ss"));
                        //ErrorCodes.WAIT_TO_RESEND_OTP_AGAIN
                        msg = "MOBILE COUNTDOWN ENDS.";
                        flag = false;
                    }
                    //不管正确与否 10分钟内只能发送5次
                    if(numberOfResendOtp > 5) {
                        //非10分钟内发送次数超过5 重置为0
                        numberOfResendOtp = 0;
                    }

                }
            }
            //'-5'  对应的result  10分钟内超过最大重试次数 激活pingcode校验 才存在重试逻辑

            if(false) {
                //将验证码存入缓存
                JedisUtil.setex(phone + "pinCode",time_resend_otp_possible, randomNumber, CodeEnum.REDIS_MSG_CODE.codeOf());
                String message_sms = MessageConstants.MESSAGE_SMS_SEND_PINCODE.replace("#{code}", randomNumber);
                SmsUtil.sendSmsContent(new SmsDto(null,country.getDial(),phone,message_sms));
                //{"errorCode":0,"message":"Your OTP has been sent via SMS to your mobile.","data":{"countdown_timer_resend_otp":30,"resend_otp_possible":"2023/08/28 09:27:52"}}
                TblMember tblMember = new TblMember();
                tblMember.setId(memberInfo.getId());
                //pingcode 激活码也是用该字段
                tblMember.setActiveCode(randomNumber);
                tblMember.setNumberOfResendOtp(numberOfResendOtp + 1);
                tblMember.setRequestActiveDate(new Date());
                this.updateEntityById(tblMember);

                Date date = Kit.plusSecond(time_resend_otp_possible, new Date());
                resultData.put("resend_otp_possible",Kit.formatGmtDate(date, "yyyy/MM/dd HH:mm:ss"));
                resultData.put("countdown_timer_resend_otp", time_resend_otp_possible);
                return ResponseResultApp.success(ErrorStringConstants.RESEND_OTP_SUCCESS,resultData);
            }
        } else {
            //已校验账号为空  dtInfo.Rows[0]["active_code"].ToString() == "0" //'0'  对应的result   '-2' 对应账号已存在
            msg = "ErrorString.MOBILE_NOT_FOUND";
            errorCode = 400;
        }
        responseResultApp.setMessage(msg);
        responseResultApp.setErrorCode(errorCode);
        responseResultApp.setData(resultData);
        TblMember tblMember = new TblMember();
        tblMember.setId(memberInfo.getId());
        //校验不通过 重试次数+1
        tblMember.setNumberOfResendOtp(numberOfResendOtp + 1);
        //pingcode 激活码也是用该字段
        tblMember.setActiveCode(randomNumber);
        tblMember.setRequestActiveDate(new Date());
        this.updateEntityById(tblMember);
        return responseResultApp;
    }

    @Override
    public ResponseResultApp updatePassword(UpdatePasswordDto dto) {
        TblMember curLoginMember = UserUtil.getCurLoginMember();
        curLoginMember = this.findEntityById(curLoginMember.getId());
        //读取配置文件ConfigurationManager  TODO 是否校验密码
        boolean isValid = checkUserPasswordIsValid(dto.getNew_password());
        if(isValid) {
            String hash = PBKDF2Util.createHashPassword(dto.getNew_password(),curLoginMember.getIterationPassword(),curLoginMember.getSaltPassword());
            // 校验登录密码
            boolean b = PBKDF2Util.verifyPassword(curLoginMember.getPassword(), curLoginMember.getIterationPassword(), hash);
            if(b) {
                //与上次（最后一次）密码相同
                return ResponseResultApp.error("ErrorCodes.PASSWORD_REUSE, ErrorString.PASSWORD_REUSE");
            }else {
                //更新新密码
                TblMember member = new TblMember();
                member.setId(curLoginMember.getId());
                Integer iterationPassword = PBKDF2Util.generateIterationPassword();
                String saltPassword = PBKDF2Util.generateSaltPassword();
                String hashPassword = PBKDF2Util.createHashPassword(dto.getNew_password(), iterationPassword, saltPassword);
                member.setPassword(hashPassword);
                member.setIterationPassword(iterationPassword);
                member.setSaltPassword(saltPassword);
                int i = this.memberMapper.updateByPrimaryKeySelective(member);
                if(i > 0) {
                    //修改密码需要重新登录 ？？
                    /*String oldToken = JedisUtil.get(curLoginMember.getId(), CodeEnum.REDIS_USER.codeOf());
                    if(StringUtils.isNotEmpty(oldToken) && !"dev".equals(oldToken)){
                        JedisUtil.del(oldToken, CodeEnum.REDIS_USER.codeOf());
                        JedisUtil.del(curLoginMember.getId(), CodeEnum.REDIS_USER.codeOf());
                    }*/
                }
            }
            //apiResult = new ApiResult(ErrorCodes.OK, ErrorString.UPDATE_PASSWORD_SUCCESS, null);
            return ResponseResultApp.success("UPDATE_PASSWORD_SUCCESS");
        }else {
            return ResponseResultApp.error("CURRENT_PASSWORD_INCORRECT");
        }

    }

    @Override
    public ResponseResultApp getTwilioToken(GetTwilioTokenDto dto) {
        TblMember curLoginMember = UserUtil.getCurLoginMember();
        String twilioVideoToken = TwilioVoiceCallUtil.getTwilioVideoToken(curLoginMember.getId(), dto.getRoomId());
        return ResponseResultApp.success(twilioVideoToken);
    }

    private boolean checkUserPasswordIsValid(String new_password) {
        boolean flag = true;
        //读取配置文件ConfigurationManager  TODO 是否校验密码
        String check_regular_expression_password = "false";
        if("false".equals(check_regular_expression_password)) {
            return flag;
        }
        flag = Kit.isValidPassword(new_password);
        return flag;
    }


    /**
        * @description 校验短信发送频率
        * @date     2024/1/26
        * @author   cloud fly
        * @return  boolean  true 不能发送，false 可以发送
        */
    public boolean  isHitRateLimit(String phoneCountryId){
        TblCountry countryDataSet = this.tblCountryMapper.selectByPrimaryKey(phoneCountryId);
        if (countryDataSet == null ||countryDataSet.getIsSmsWhitelistCountry() == null) {
            return true;
        }
        if(countryDataSet.getIsSmsWhitelistCountry() != 0)    {
            return false;
        }
        Integer allowance = countryDataSet.getAllowance();
        Integer lastSmsTimeStamp = countryDataSet.getLastSmsTimeStamp();
        TblCountry t = new TblCountry();
        int smsLimitCount = AppSettingsConstants.smsLimitCount;
        int duration = AppSettingsConstants.smsBlockDurationInSeconds;
        int timeNow = (int) (System.currentTimeMillis()/1000);
        allowance += (int)((timeNow - lastSmsTimeStamp) * smsLimitCount / duration);
        if (allowance > smsLimitCount){
            allowance = smsLimitCount;
        }
        if (allowance < 1){
            allowance = 0;
            return true;
        }
        allowance = allowance - 1;
        t.setId(countryDataSet.getId());
        int time = (int) (System.currentTimeMillis()/1000);
        t.setLastSmsTimeStamp(time);
        t.setAllowance(allowance);
        this.tblCountryMapper.updateByPrimaryKey(t);
        return false;
    }


    /**
        * @description 计算还需等待时间
        * @date     2024/1/25
        * @author   cloud fly
        */
    private int  valid(JSONObject resultData, TblMember curLoginMember) {
        Date lastTimeRetryOtp = curLoginMember.getLastTimeRetryOtp();
        int waiting_time_of_exceeding_number_resending_otp = 10;
        Date date = Kit.plusMinute(waiting_time_of_exceeding_number_resending_otp, lastTimeRetryOtp);
        String dateByTimeZone = Kit.getDateByTimeZone(date, 0, "yyyy/MM/dd HH:mm:ss");
        resultData.put("retry_otp_possible",dateByTimeZone);
        long l = lastTimeRetryOtp.getTime() / 1000;
        long currentTimeMillis = System.currentTimeMillis()/1000;
        long l1 = currentTimeMillis - l;
        BigDecimal bigDecimal = BigDecimal.valueOf(l1).divide(BigDecimal.valueOf(60)).setScale(0, RoundingMode.CEILING);
        //已经等待时间
        int waiting_time = bigDecimal.intValue();
        waiting_time = waiting_time_of_exceeding_number_resending_otp - waiting_time;
        //剩余等待时间
        return  waiting_time;
    }

    public void sendRequestPinCodeEmail(String sEmail, String sFull_Name, String sActive_Code, String country_of_residence) {
        StringBuilder sContent = new StringBuilder();
        String sApp_Name = ContextConstants.APP_NAME;
        String timeOTPValid = this.baseSysCodeService.getCodeValue("Time_Out_Resend_Pin_Code",null);
        if (StringUtil.isEmpty(timeOTPValid)) {
            timeOTPValid = "5";
        }

        String contactUsEmail = "contactus@whitecoat.com.sg";
        if (CoreAppConstants.COUNTRY_INDONESIA_ID.equalsIgnoreCase(country_of_residence)) {
            // 印尼国家ID
            contactUsEmail = "help@whitecoat.co.id";
        }
        else if (CoreAppConstants.COUNTRY_VIETNAM_ID.equalsIgnoreCase(country_of_residence)) {
            // 越南国家ID
            contactUsEmail = "support@whitecoat.vn";
        }
        String sSubject = "WhiteCoat OTP Verification";
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append("WhiteCoat OTP Verification");
        stringBuffer.append("<p>Hi "+sFull_Name+", </p>");
        stringBuffer.append("<p>Please enter the One-Time Password (OTP) in your WhiteCoat app to log in to your account:<p>");
        stringBuffer.append("<p style='font-size:20px;'><strong>"+sActive_Code+"</strong><p>");
        stringBuffer.append("<p>This OTP is valid for only "+timeOTPValid+" minutes. If you require any assistance, please contact us at <a href='#'>"+contactUsEmail+"</a>.</p>");
        stringBuffer.append("<p>Thank you for using WhiteCoat.</p>");
        stringBuffer.append("<p>Please do not reply to this email as it is auto-generated.</p>");
        try {
            //异步处理邮件发送业务
            CompletableFuture.runAsync(() -> {
                MailgunUtil.sendEmail(sEmail, sSubject,stringBuffer.toString(), null);
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @NotNull
    public String getUserNumber() {
        //生成用户唯一编号
        String s = Kit.generateRandomString(20);
        List<String> whereList = new ArrayList<>();
        whereList.clear();
        whereList.add("del_flag=0");
        whereList.add("member_no= '"+s+"'");
        List<TblMember> listByLimit = this.findListByLimit(TblMember.class, whereList, null);
        if(listByLimit != null && listByLimit.size() > 0) {
            s = getUserNumber();
        }
        return s;
    }
}